2012-09-07 00:23:00
前几天山寨了ATL的COM_INTERFACE,了解了一个COM类的如何进行通用的组织。今天再来学习下COM协议,看看如何实现一个COM组件——当然,也是不能用ATL的,不然就学不到什么了。
COM DLL说简单简单,说复杂也很复杂。说简单呢,其实貌似只要导出下面这五个函数就可以了:
(我有点怀疑但不确定DllInstall是不是后来加的,本文中我们先不理它。)
前四个函数中,后两个是注册与反注册,就是写写注册表的事情,简单。前面两个,特别是DllGetClassObject,比较关键。
先不研究这些,我们先按前两天的方法写个COM类吧。
首先,我们定义一个接口 ISampleInterface,以及一个类CSampleClass Interface.h
1#include <Unknwn.h>
2
3struct __declspec(uuid("{83C783E3-F989-4E0D-BFC5-631273EDFFDA}"))
4ISampleInterface : public IUnknown
5{
6 STDMETHOD(SampleMethod)() PURE;
7};
8
9class __declspec(uuid("{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"))
10SampleClass;
与前面不同的是,这里两个声明都写上了一个UUID,前一个是接口ID(IID),后一个是类ID(CLSID)。这个文件将是提供给COM的使用者的。
SampleClass.h
1class SampleClass : public xl::ComClass<SampleClass>,
2 xl::IUnknownImpl<ISampleInterface>
3{
4public:
5 SampleClass();
6 ~SampleClass();
7
8public:
9 STDMETHOD(SampleMethod)();
10
11public:
12 XL_COM_INTERFACE_BEGIN(SampleClass)
13 XL_COM_INTERFACE(ISampleInterface)
14 XL_COM_INTERFACE_END()
15};
SampleClass.cpp
1SampleClass::SampleClass()
2{
3 InterlockedIncrement(&g_nModuleCount);
4}
5
6SampleClass::~SampleClass()
7{
8 InterlockedDecrement(&g_nModuleCount);
9}
10
11STDMETHODIMP SampleClass::SampleMethod()
12{
13 MessageBox(NULL, _T("SampleMethod called."), _T("Info"), MB_OK | MB_ICONINFORMATION);
14 return S_OK;
15}
SampleMethod 就简单调用一个MessageBox意思一下。构造函数和析构函数中两行先不看,后面解释。
这个函数的MSDN文档见: http://msdn.microsoft.com/en-us/library/windows/desktop/ms690368.aspx
函数原型为:
1HRESULT __stdcall DllCanUnloadNow(void);
函数实现的要求是,当此DLL内的所有COM对象都消亡了的时候,返回S_OK;如果还有COM对象存在,就返回S_FALSE。
现在我们有一个COM类SampleClass,它可能被创建一次,然后引用计数加加减减;也可能被创建多次,每个实例的引用计数同样会被加加减减。当引用计数被减为0的时候,对象将消亡(析构函数被调用)。因此,我们在对象的构造和析构的地方埋点就可以了。
定义一个全局变量:
1LONG g_nModuleCount = 0;
然后看到刚才的SampleClass的构造函数和析构函数中的灰色代码,就可以存对象的创建/销毁计数了。对于多个COM对象的情况,也可以这么搞。
然后,DllCanUnloadNow的实现就很简单了:
1STDAPI DllCanUnloadNow()
2{
3 return g_nModuleCount > 0 ? S_FALSE : S_OK;
4}
终于到关键的地方了。以前一直弄不懂这个函数。它的MSDN文档见: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680760.aspx
函数原型为:
1HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv);
第一个参数是要创建的对象的CLSID,很明确。第二个参数有点迷惑,MSDN原文是:
A reference to the identifier of the interface that the caller is to use to communicate with the class object. Usually, this is IID_IClassFactory (defined in the OLE headers as the interface identifier for IClassFactory).
“Usually”,它是IID_IClassFactory。我不知道有没有不“Usually”的情况,也不知道这个接口原先的设计意图是什么。如果可能,其实完全可以绕开类厂机制,直接用想要使用的那个接口的IID,貌似整套机制也能运转……有木有达人解释下它的渊源?
不过呢,目前我就把它当作IID_IClassFactory,其他一律不支持。
我们先要实现一个“类厂”——一个继承于IClassFactory的COM类:
ClassFactory.h
1class ClassFactory : public xl::ComClass<ClassFactory>,
2 public xl::IClassFactoryImpl<>
3{
4public:
5 ClassFactory(REFCLSID rclsid);
6 ~ClassFactory();
7
8public:
9 STDMETHOD(CreateInstance)(_In_opt_ IUnknown *pUnkOuter,
10 _In_ REFIID riid,
11 _COM_Outptr_ void **ppvObject);
12
13public:
14 XL_COM_INTERFACE_BEGIN(ClassFactory)
15 XL_COM_INTERFACE(IClassFactory)
16 XL_COM_INTERFACE_END()
17
18private:
19 CLSID m_clsid;
20};
IClassFactory有两个方法,CreateInstance和LockServer。我们只实现前者,它用于创建一个对象,第一个参数不理它,第二个参数是IID,第三个参数用于输出。CLSID由构造函数传入,保存在m_clsid中——类厂是与COM类一一对应的。
ClassFactory.cpp
1ClassFactory::ClassFactory(REFCLSID rclsid)
2{
3 m_clsid = rclsid;
4}
5
6ClassFactory::~ClassFactory()
7{
8
9}
10
11STDMETHODIMP ClassFactory::CreateInstance(_In_opt_ IUnknown *pUnkOuter,
12 _In_ REFIID riid,
13 _COM_Outptr_ void **ppvObject)
14{
15 if (riid == __uuidof(ISampleInterface) && m_clsid == __uuidof(SampleClass))
16 {
17 ISampleInterface *p = new SampleClass;
18 p->QueryInterface(riid, ppvObject);
19
20 return S_OK;
21 }
22
23 return CLASS_E_CLASSNOTAVAILABLE;
24}
这里只对IID为__uuidof(ISampleInterface)且CLSID为__uuidof(SampleClass))的情况作了响应,创建一个SampleClass对象。注意到创建对象之后有一次QueryInterface,在这里面会做一次AddRef操作,因此引用计数此时为1。其实AddRef可以不出现在代码中,需要的时候就用QueryInterface代替。
这些都准备好了,最后来实现DllGetClassObject:
1STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID *ppv)
2{
3 if (riid == __uuidof(IClassFactory) && rclsid == __uuidof(SampleClass))
4 {
5 IClassFactory *p = new ClassFactory(rclsid);
6 p->QueryInterface(riid, ppv);
7
8 return S_OK;
9 }
10
11 return CLASS_E_CLASSNOTAVAILABLE;
12}
这个我就直接贴代码了:
1STDAPI DllRegisterServer(void)
2{
3 TCHAR szModulePath[MAX_PATH] = {};
4 GetModuleFileName(g_hModule, szModulePath, ARRAYSIZE(szModulePath));
5
6 xl::Registry::SetString(HKEY_CLASSES_ROOT,
7 _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}\\InprocServer32"),
8 _T(""),
9 szModulePath);
10
11 xl::Registry::SetString(HKEY_CLASSES_ROOT,
12 _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}\\ProgID"),
13 _T(""),
14 _T("Streamlet.COMProvider.SampleClass.1"));
15
16 xl::Registry::SetString(HKEY_CLASSES_ROOT,
17 _T("Streamlet.COMProvider.SampleClass.1"),
18 _T(""),
19 _T("SampleClass Class"));
20
21 xl::Registry::SetString(HKEY_CLASSES_ROOT,
22 _T("Streamlet.COMProvider.SampleClass.1\\CLSID"),
23 _T(""),
24 _T("{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"));
25
26 return S_OK;
27}
28
29STDAPI DllUnregisterServer(void)
30{
31 xl::Registry::DeleteKeyRecursion(HKEY_CLASSES_ROOT,
32 _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"));
33
34 xl::Registry::DeleteKeyRecursion(HKEY_CLASSES_ROOT,
35 _T("Streamlet.COMProvider.SampleClass.1"));
36
37 return S_OK;
38}
注意,我这里并没有写得很全,只是注册几项必要的。至此,我们的COM组件实现完毕。
编译刚才的DLL,并使用regsvr32注册(注意管理员权限)。然后写一个小程序来调用之:
1#include <tchar.h>
2#include <Objbase.h>
3#include "../COMProvider/Interface.h"
4
5int _tmain(int argc, TCHAR *argv[])
6{
7 HRESULT hr = CoInitialize(NULL);
8
9 ISampleInterface *pSampleInterface = nullptr;
10 hr = CoCreateInstance(__uuidof(SampleClass),
11 nullptr,
12 CLSCTX_INPROC_SERVER,
13 __uuidof(ISampleInterface),
14 (LPVOID *)&pSampleInterface);
15
16 if (SUCCEEDED(hr))
17 {
18 pSampleInterface->SampleMethod();
19 pSampleInterface->Release();
20 }
21
22 CoUninitialize();
23
24 return 0;
25}
运行结果:

上述例子代码见COMProtocol.rar(http://pan.baidu.com/s/1c0GSI7u),库依赖见http://xllib.codeplex.com/。当然,现在只是简单地迎合了一下CoCreateInstance,还有许多其他事情要做,且听下回分解。
首发:http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html